/* ========================================================================
 * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved.
 *
 * OPC Foundation MIT License 1.00
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 *
 * The complete license agreement can be found here:
 * http://opcfoundation.org/License/MIT/1.00/
 * ======================================================================*/

using System;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Opc.Ua.Security;
using Opc.Ua.Security.Certificates;

namespace Opc.Ua.Bindings
{
    /// <summary>
    /// Manages the server side of a UA TCP channel.
    /// </summary>
    public class TcpServerChannel : TcpListenerChannel
    {
        /// <summary>
        /// Attaches the object to an existing socket.
        /// </summary>
        public TcpServerChannel(
            string contextId,
            ITcpChannelListener listener,
            BufferManager bufferManager,
            ChannelQuotas quotas,
            CertificateTypesProvider serverCertificateTypesProvider,
            EndpointDescriptionCollection endpoints,
            ITelemetryContext telemetry)
            : base(
                contextId,
                listener,
                bufferManager,
                quotas,
                serverCertificateTypesProvider,
                endpoints,
                telemetry)
        {
            m_logger = telemetry.CreateLogger<TcpServerChannel>();
            m_queuedResponses = [];
        }

        /// <summary>
        /// An overrideable version of the Dispose.
        /// </summary>
        protected override void Dispose(bool disposing)
        {
            lock (DataLock)
            {
                base.Dispose(disposing);
            }
        }

        /// <summary>
        /// The channel name used in trace output.
        /// </summary>
        public override string ChannelName => "TCPSERVERCHANNEL";

        /// <summary>
        /// The URL used to establish a connection to the client.
        /// </summary>
        public Uri ReverseConnectionUrl { get; internal set; }

        /// <summary>
        /// Raised when the connection status changes.
        /// </summary>
        public event TcpChannelStatusEventHandler StatusChanged;

#pragma warning disable CS0618 // Type or member is obsolete
        private class ReverseConnectAsyncResult : AsyncResultBase
#pragma warning restore CS0618 // Type or member is obsolete
        {
            public ReverseConnectAsyncResult(
                AsyncCallback callback,
                object callbackData,
                int timeout,
                ILogger logger)
                : base(callback, callbackData, timeout, logger)
            {
            }

            public IMessageSocket Socket;
        }

        /// <summary>
        /// Begin a reverse connect.
        /// </summary>
        public IAsyncResult BeginReverseConnect(
            uint channelId,
            Uri endpointUrl,
            AsyncCallback callback,
            object callbackData,
            int timeout)
        {
            ChannelId = channelId;
            ReverseConnectionUrl = endpointUrl;
            SetEndpointUrl(Listener.EndpointUrl.ToString());

            var ar = new ReverseConnectAsyncResult(callback, callbackData, timeout, m_logger);

            var tcpMessageSocketFactory = new TcpMessageSocketFactory(Telemetry);
            ar.Socket = Socket = tcpMessageSocketFactory.Create(
                this,
                BufferManager,
                ReceiveBufferSize);

            var connectComplete = new EventHandler<IMessageSocketAsyncEventArgs>(
                OnReverseConnectComplete);
            Socket.BeginConnect(endpointUrl, connectComplete, ar);

            return ar;
        }

        /// <summary>
        /// End the reverse connect.
        /// </summary>
        /// <exception cref="ArgumentException"></exception>
        /// <exception cref="TimeoutException"></exception>
        public void EndReverseConnect(IAsyncResult result)
        {
            if (result is not ReverseConnectAsyncResult ar)
            {
                throw new ArgumentException(
                    "EndReverseConnect is called with invalid IAsyncResult.",
                    nameof(result));
            }

            if (!ar.WaitForComplete())
            {
                throw new TimeoutException();
            }
        }

        /// <summary>
        /// Reverse client is connected, send reverse hello message.
        /// </summary>
        private void OnReverseConnectComplete(object sender, IMessageSocketAsyncEventArgs result)
        {
            var ar = (ReverseConnectAsyncResult)result.UserToken;

            if (ar == null || m_pendingReverseHello != null)
            {
                return;
            }

            if (result.IsSocketError)
            {
                ar.Exception = new ServiceResultException(
                    StatusCodes.BadNotConnected,
                    result.SocketErrorString);
                ar.OperationCompleted();
                return;
            }

            byte[] buffer = BufferManager.TakeBuffer(
                SendBufferSize,
                "OnReverseConnectConnectComplete");

            try
            {
                // start reading messages.
                ar.Socket.ReadNextMessage();

                // send reverse hello message.
                using var encoder = new BinaryEncoder(
                    buffer,
                    0,
                    SendBufferSize,
                    Quotas.MessageContext);
                encoder.WriteUInt32(null, TcpMessageType.ReverseHello);
                encoder.WriteUInt32(null, 0);
                encoder.WriteString(null, EndpointDescription.Server.ApplicationUri);
                encoder.WriteString(null, EndpointDescription.EndpointUrl);
                int size = encoder.Close();
                UpdateMessageSize(buffer, 0, size);

                // set state to waiting for hello.
                State = TcpChannelState.Connecting;
                m_pendingReverseHello = ar;

                BeginWriteMessage(new ArraySegment<byte>(buffer, 0, size), null);
                buffer = null;
            }
            catch (Exception e)
            {
                ar.Exception = e;
                ar.OperationCompleted();
            }
            finally
            {
                if (buffer != null)
                {
                    BufferManager.ReturnBuffer(buffer, "OnReverseConnectComplete");
                }
            }
        }

        /// <summary>
        /// Handles a reconnect request.
        /// </summary>
        /// <exception cref="ArgumentNullException"><paramref name="socket"/> is <c>null</c>.</exception>
        /// <exception cref="ServiceResultException"></exception>
        public override void Reconnect(
            IMessageSocket socket,
            uint requestId,
            uint sequenceNumber,
            X509Certificate2 clientCertificate,
            ChannelToken token,
            OpenSecureChannelRequest request)
        {
            if (socket == null)
            {
                throw new ArgumentNullException(nameof(socket));
            }

            lock (DataLock)
            {
                // make sure the same client certificate is being used.
                CompareCertificates(ClientCertificate, clientCertificate, false);

                // check for replay attacks.
                if (!VerifySequenceNumber(sequenceNumber, "Reconnect"))
                {
                    throw new ServiceResultException(StatusCodes.BadSequenceNumberInvalid);
                }

                try
                {
                    m_logger.LogInformation(
                        "{Channel} SOCKET RECONNECTED: {SocketHandle:X8}, ChannelId={ChannelId}",
                        ChannelName,
                        socket.Handle,
                        ChannelId);

                    // replace the socket.
                    Socket = socket;
                    Socket.ChangeSink(this);

                    // need to assign a new token id.
                    token.TokenId = GetNewTokenId();

                    // put channel back in open state.
                    ActivateToken(token);
                    State = TcpChannelState.Open;

                    // send response.
                    SendOpenSecureChannelResponse(requestId, token, request);

                    // send any queued responses.
                    ResetQueuedResponses(OnChannelReconnected);
                }
                catch (Exception e)
                {
                    SendServiceFault(
                        token,
                        requestId,
                        ServiceResult.Create(
                            e,
                            StatusCodes.BadTcpInternalError,
                            "Unexpected error processing request."));
                }
            }
        }

        /// <summary>
        /// Processes an incoming message.
        /// </summary>
        /// <returns>True if the implementor takes ownership of the buffer.</returns>
        protected override bool HandleIncomingMessage(
            uint messageType,
            ArraySegment<byte> messageChunk)
        {
            lock (DataLock)
            {
                SetResponseRequired(true);

                try
                {
                    // process a response.
                    if (TcpMessageType.IsType(messageType, TcpMessageType.Message))
                    {
                        // m_logger.LogTrace(
                        //     Utils.TraceMasks.ServiceDetail,
                        //     "ChannelId {Id}: ProcessRequestMessage",
                        //     ChannelId);
                        return ProcessRequestMessage(messageType, messageChunk);
                    }

                    // check for hello.
                    if (messageType == TcpMessageType.Hello)
                    {
                        m_logger.LogDebug(
                            Utils.TraceMasks.ServiceDetail,
                            "ChannelId {Id}: ProcessHelloMessage",
                            ChannelId);
                        return ProcessHelloMessage(messageChunk);
                    }

                    // process open secure channel repsonse.
                    if (TcpMessageType.IsType(messageType, TcpMessageType.Open))
                    {
                        m_logger.LogDebug(
                            Utils.TraceMasks.ServiceDetail,
                            "ChannelId {Id}: ProcessOpenSecureChannelRequest",
                            ChannelId);
                        return ProcessOpenSecureChannelRequest(messageType, messageChunk);
                    }

                    // process close secure channel response.
                    if (TcpMessageType.IsType(messageType, TcpMessageType.Close))
                    {
                        m_logger.LogDebug(
                            Utils.TraceMasks.ServiceDetail,
                            "ChannelId {Id}: ProcessCloseSecureChannelRequest",
                            ChannelId);
                        return ProcessCloseSecureChannelRequest(messageType, messageChunk);
                    }

                    // invalid message type - must close socket and reconnect.
                    ForceChannelFault(
                        StatusCodes.BadTcpMessageTypeInvalid,
                        "The server does not recognize the message type: {0:X8}.",
                        messageType);

                    return false;
                }
                finally
                {
                    SetResponseRequired(false);
                }
            }
        }

        /// <summary>
        /// Called to send queued responses after a reconnect.
        /// </summary>
        private void OnChannelReconnected(object state)
        {
            if (state is not SortedDictionary<uint, IServiceResponse> responses)
            {
                return;
            }

            foreach (KeyValuePair<uint, IServiceResponse> response in responses)
            {
                try
                {
                    SendResponse(response.Key, response.Value);
                }
                catch (Exception e)
                {
                    m_logger.LogError(e, "Unexpected error re-sending request (ID={Id}).", response.Key);
                }
            }
        }

        /// <summary>
        /// Processes a Hello message from the client.
        /// </summary>
        private bool ProcessHelloMessage(ArraySegment<byte> messageChunk)
        {
            const uint kProtocolVersion = 0;
            const int kResponseBufferSize = 127;

            // Communication is active on the channel
            UpdateLastActiveTime();

            // validate the channel state.
            if (State != TcpChannelState.Connecting)
            {
                ForceChannelFault(
                    StatusCodes.BadTcpMessageTypeInvalid,
                    "Client sent an unexpected Hello message.");
                return false;
            }

            try
            {
                // read requested buffer sizes.
                uint protocolVersion;
                uint receiveBufferSize;
                uint sendBufferSize;
                uint maxMessageSize;
                uint maxChunkCount;

                using (var decoder = new BinaryDecoder(messageChunk, Quotas.MessageContext))
                {
                    ReadAndVerifyMessageTypeAndSize(
                        decoder,
                        TcpMessageType.Hello,
                        messageChunk.Count);

                    // read requested buffer sizes.
                    protocolVersion = decoder.ReadUInt32(null);
                    // note: swapped the send and receive buffer size read to reflect the server view
                    sendBufferSize = decoder.ReadUInt32(null);
                    receiveBufferSize = decoder.ReadUInt32(null);
                    maxMessageSize = decoder.ReadUInt32(null);
                    maxChunkCount = decoder.ReadUInt32(null);

                    // read the endpoint url.
                    int length = decoder.ReadInt32(null);

                    if (length > 0)
                    {
                        if (length > TcpMessageLimits.MaxEndpointUrlLength)
                        {
                            ForceChannelFault(StatusCodes.BadTcpEndpointUrlInvalid);
                            return false;
                        }

                        byte[] endpointUrl = new byte[length];

                        for (int ii = 0; ii < endpointUrl.Length; ii++)
                        {
                            endpointUrl[ii] = decoder.ReadByte(null);
                        }

                        if (!SetEndpointUrl(
                            Encoding.UTF8.GetString(endpointUrl, 0, endpointUrl.Length)))
                        {
                            ForceChannelFault(StatusCodes.BadTcpEndpointUrlInvalid);
                            return false;
                        }
                    }
                }

                // update receive buffer size.
                ReceiveBufferSize = Math.Min(ReceiveBufferSize, (int)receiveBufferSize);
                ReceiveBufferSize = Math.Min(
                    Math.Max(ReceiveBufferSize, TcpMessageLimits.MinBufferSize),
                    TcpMessageLimits.MaxBufferSize);

                // update send buffer size.
                SendBufferSize = Math.Min(SendBufferSize, (int)sendBufferSize);
                SendBufferSize = Math.Min(
                    Math.Max(SendBufferSize, TcpMessageLimits.MinBufferSize),
                    TcpMessageLimits.MaxBufferSize);

                // update the max message size.
                if (maxMessageSize > 0 && maxMessageSize < MaxResponseMessageSize)
                {
                    MaxResponseMessageSize = (int)maxMessageSize;
                }

                if (MaxResponseMessageSize < SendBufferSize)
                {
                    MaxResponseMessageSize = SendBufferSize;
                }

                // update the max chunk count.
                MaxResponseChunkCount = CalculateChunkCount(MaxResponseMessageSize, SendBufferSize);

                if (maxChunkCount > 0 && maxChunkCount < MaxResponseChunkCount)
                {
                    MaxResponseChunkCount = (int)maxChunkCount;
                }

                MaxRequestChunkCount = CalculateChunkCount(
                    MaxRequestMessageSize,
                    ReceiveBufferSize);

                // send acknowledge.
                byte[] buffer = BufferManager.TakeBuffer(
                    kResponseBufferSize,
                    nameof(ProcessHelloMessage));

                try
                {
                    using (var ostrm = new MemoryStream(buffer, 0, kResponseBufferSize))
                    using (var encoder = new BinaryEncoder(ostrm, Quotas.MessageContext, false))
                    {
                        encoder.WriteUInt32(null, TcpMessageType.Acknowledge);
                        encoder.WriteUInt32(null, 0);
                        encoder.WriteUInt32(null, kProtocolVersion);
                        encoder.WriteUInt32(null, (uint)ReceiveBufferSize);
                        encoder.WriteUInt32(null, (uint)SendBufferSize);
                        encoder.WriteUInt32(null, (uint)MaxRequestMessageSize);
                        encoder.WriteUInt32(null, (uint)MaxRequestChunkCount);

                        int size = encoder.Close();
                        UpdateMessageSize(buffer, 0, size);

                        // now ready for the open or bind request.
                        State = TcpChannelState.Opening;

                        BeginWriteMessage(new ArraySegment<byte>(buffer, 0, size), null);
                    }
                    buffer = null;
                }
                finally
                {
                    if (buffer != null)
                    {
                        BufferManager.ReturnBuffer(buffer, nameof(ProcessHelloMessage));
                    }
                }
            }
            catch (Exception e)
            {
                ForceChannelFault(
                    e,
                    StatusCodes.BadTcpInternalError,
                    "Unexpected error while processing a Hello message.");
            }

            return false;
        }

        /// <summary>
        /// Processes an OpenSecureChannel request message.
        /// </summary>
        /// <exception cref="ServiceResultException"></exception>
        private bool ProcessOpenSecureChannelRequest(
            uint messageType,
            ArraySegment<byte> messageChunk)
        {
            // Communication is active on the channel
            UpdateLastActiveTime();

            // validate the channel state.
            if (State is not TcpChannelState.Opening and not TcpChannelState.Open)
            {
                ForceChannelFault(
                    StatusCodes.BadTcpMessageTypeInvalid,
                    "Client sent an unexpected OpenSecureChannel message.");
                return false;
            }

            // parse the security header.
            uint channelId = 0;
            X509Certificate2 clientCertificate = null;
            uint requestId = 0;
            uint sequenceNumber = 0;

            ArraySegment<byte> messageBody;

            try
            {
                messageBody = ReadAsymmetricMessage(
                    messageChunk,
                    ServerCertificate,
                    out channelId,
                    out clientCertificate,
                    out requestId,
                    out sequenceNumber);

                // check for replay attacks.
                if (!VerifySequenceNumber(sequenceNumber, "ProcessOpenSecureChannelRequest"))
                {
                    throw new ServiceResultException(StatusCodes.BadSequenceNumberInvalid);
                }
            }
            catch (Exception e)
            {
                const string errorSecurityChecksFailed
                    = "Could not verify security on OpenSecureChannel request.";

                m_logger.LogError(e, errorSecurityChecksFailed);

                // report the audit event for open secure channel
                ReportAuditOpenSecureChannelEvent?.Invoke(this, null, clientCertificate, e);

                // report the audit event for open certificate error
                ReportAuditCertificateEvent?.Invoke(clientCertificate, e);

                // If the certificate structure, signature and trust list checks pass,
                // return the other specific validation errors instead of BadSecurityChecksFailed
                if (e.InnerException is ServiceResultException innerException)
                {
                    if (innerException.StatusCode == StatusCodes.BadCertificateUntrusted ||
                        innerException.StatusCode == StatusCodes.BadCertificateChainIncomplete ||
                        innerException.StatusCode == StatusCodes.BadCertificateRevoked ||
                        innerException.StatusCode == StatusCodes.BadCertificateInvalid ||
                        innerException.StatusCode == StatusCodes.BadCertificatePolicyCheckFailed ||
                        (
                            innerException.InnerResult != null &&
                            innerException.InnerResult.StatusCode == StatusCodes
                                .BadCertificateUntrusted))
                    {
                        ForceChannelFault(
                            StatusCodes.BadSecurityChecksFailed,
                            errorSecurityChecksFailed);
                        return false;
                    }
                    else if (innerException.StatusCode
                        is StatusCodes.BadCertificateTimeInvalid
                            or StatusCodes.BadCertificateIssuerTimeInvalid
                            or StatusCodes.BadCertificateHostNameInvalid
                            or StatusCodes.BadCertificateUriInvalid
                            or StatusCodes.BadCertificateUseNotAllowed
                            or StatusCodes.BadCertificateIssuerUseNotAllowed
                            or StatusCodes.BadCertificateRevocationUnknown
                            or StatusCodes.BadCertificateIssuerRevocationUnknown
                            or StatusCodes.BadCertificateIssuerRevoked)
                    {
                        ForceChannelFault(innerException, innerException.StatusCode, e.Message);
                        return false;
                    }
                }

                ForceChannelFault(StatusCodes.BadSecurityChecksFailed, errorSecurityChecksFailed);
                return false;
            }

            BufferCollection chunksToProcess = null;
            OpenSecureChannelRequest request = null;
            ChannelToken token = null;
            try
            {
                bool firstCall = ClientCertificate == null;

                // must ensure the same certificate was used.
                if (ClientCertificate != null)
                {
                    CompareCertificates(ClientCertificate, clientCertificate, false);
                }
                else
                {
                    ClientCertificate = clientCertificate;
                }

                // check if it is necessary to wait for more chunks.
                if (!TcpMessageType.IsFinal(messageType))
                {
                    SaveIntermediateChunk(requestId, messageBody, true);
                    return false;
                }
                // get the chunks to process.
                chunksToProcess = GetSavedChunks(requestId, messageBody, true);

                request = (OpenSecureChannelRequest)
                    BinaryDecoder.DecodeMessage(
                        new ArraySegmentStream(chunksToProcess),
                        typeof(OpenSecureChannelRequest),
                        Quotas.MessageContext);

                if (request == null)
                {
                    throw ServiceResultException.Create(
                        StatusCodes.BadStructureMissing,
                        "Could not parse OpenSecureChannel request body.");
                }

                // check the security mode.
                if (request.SecurityMode != SecurityMode)
                {
                    ReviseSecurityMode(firstCall, request.SecurityMode);
                }

                // create a new token.
                token = CreateToken();
                token.TokenId = GetNewTokenId();
                token.ServerNonce = CreateNonce(ServerCertificate);
                // check the client nonce.
                token.ClientNonce = request.ClientNonce;
                if (!ValidateNonce(ClientCertificate, token.ClientNonce))
                {
                    throw ServiceResultException.Create(
                        StatusCodes.BadNonceInvalid,
                        "Client nonce is not the correct length or not random enough.");
                }

                // choose the lifetime.
                int lifetime = (int)request.RequestedLifetime;

                if (lifetime < TcpMessageLimits.MinSecurityTokenLifeTime)
                {
                    lifetime = TcpMessageLimits.MinSecurityTokenLifeTime;
                }

                if (lifetime > 0 && lifetime < token.Lifetime)
                {
                    token.Lifetime = lifetime;
                }

                // check the request type.
                SecurityTokenRequestType requestType = request.RequestType;

                if (requestType == SecurityTokenRequestType.Issue &&
                    State != TcpChannelState.Opening)
                {
                    throw ServiceResultException.Create(
                        StatusCodes.BadRequestTypeInvalid,
                        "Cannot request a new token for an open channel.");
                }

                if (requestType == SecurityTokenRequestType.Renew && State != TcpChannelState.Open)
                {
                    // may be reconnecting to a dropped channel.
                    if (State == TcpChannelState.Opening)
                    {
                        // tell the listener to find the channel that can process the request.
                        Listener.ReconnectToExistingChannel(
                            Socket,
                            requestId,
                            sequenceNumber,
                            channelId,
                            ClientCertificate,
                            token,
                            request);

                        token = null;

                        m_logger.LogInformation(
                            "{Channel} ReconnectToExistingChannel Socket={SocketHandle:X8}, ChannelId={ChannelId}, TokenId={TokenId}",
                            ChannelName,
                            (Socket?.Handle) ?? 0,
                            CurrentToken != null ? CurrentToken.ChannelId : 0,
                            CurrentToken != null ? CurrentToken.TokenId : 0);

                        // close the channel.
                        ChannelClosed();

                        // nothing more to do.
                        return false;
                    }

                    throw ServiceResultException.Create(
                        StatusCodes.BadRequestTypeInvalid,
                        "Cannot request to renew a token for a channel that has not been opened.");
                }

                // check the channel id.
                if (requestType == SecurityTokenRequestType.Renew && channelId != ChannelId)
                {
                    throw ServiceResultException.Create(
                        StatusCodes.BadTcpSecureChannelUnknown,
                        "Do not recognize the secure channel id provided.");
                }

                // log security information.
                if (requestType == SecurityTokenRequestType.Issue)
                {
                    m_logger.SecureChannelCreated(
                        s_implementationString,
                        Listener.EndpointUrl.ToString(),
                        Utils.Format("{0}", ChannelId),
                        EndpointDescription,
                        ClientCertificate,
                        ServerCertificate,
                        BinaryEncodingSupport.Required);
                }
                else
                {
                    m_logger.SecureChannelRenewed(s_implementationString, Utils.Format("{0}", ChannelId));
                }

                if (requestType == SecurityTokenRequestType.Renew)
                {
                    SetRenewedToken(token);
                }
                else
                {
                    ActivateToken(token);
                }

                // ensure the token is not disposed
                token = null;

                State = TcpChannelState.Open;

                // send the response.
                if (requestType == SecurityTokenRequestType.Renew)
                {
                    SendOpenSecureChannelResponse(requestId, RenewedToken, request);
                }
                else
                {
                    SendOpenSecureChannelResponse(requestId, CurrentToken, request);
                }

                // notify reverse
                CompleteReverseHello(null);

                // notify any monitors.
                NotifyMonitors(ServiceResult.Good, false);

                if (requestType == SecurityTokenRequestType.Issue)
                {
                    // always report the audit event for open secure channel with RequestType = Issue
                    ReportAuditOpenSecureChannelEvent?.Invoke(
                        this,
                        request,
                        ClientCertificate,
                        null);
                }

                return false;
            }
            catch (Exception e)
            {
                // report the audit event for open secure channel
                ReportAuditOpenSecureChannelEvent?.Invoke(this, request, ClientCertificate, e);

                SendServiceFault(
                    requestId, ServiceResult.Create(
                        e,
                        StatusCodes.BadTcpInternalError,
                        "Unexpected error processing OpenSecureChannel request."));
                CompleteReverseHello(e);
                return false;
            }
            finally
            {
                Utils.SilentDispose(token);
                chunksToProcess?.Release(BufferManager, "ProcessOpenSecureChannelRequest");
            }
        }

        /// <inheritdoc/>
        protected override void NotifyMonitors(ServiceResult status, bool closed)
        {
            try
            {
                StatusChanged?.Invoke(this, status, closed);
            }
            catch (Exception e)
            {
                m_logger.LogError(e, "Error raising StatusChanged event.");
            }
        }

        /// <inheritdoc/>
        protected override void CompleteReverseHello(Exception e)
        {
            ReverseConnectAsyncResult ar = m_pendingReverseHello;
            if (ar != null &&
                ar == Interlocked.CompareExchange(ref m_pendingReverseHello, null, ar))
            {
                ar.Exception = e;
                ar.OperationCompleted();
            }
        }

        /// <summary>
        /// Sends an OpenSecureChannel response.
        /// </summary>
        private void SendOpenSecureChannelResponse(
            uint requestId,
            ChannelToken token,
            OpenSecureChannelRequest request)
        {
            m_logger.LogDebug("ChannelId {Id}: SendOpenSecureChannelResponse()", ChannelId);

            var response = new OpenSecureChannelResponse();

            response.ResponseHeader.RequestHandle = request.RequestHeader.RequestHandle;
            response.ResponseHeader.Timestamp = DateTime.UtcNow;

            response.SecurityToken.ChannelId = token.ChannelId;
            response.SecurityToken.TokenId = token.TokenId;
            response.SecurityToken.CreatedAt = token.CreatedAt;
            response.SecurityToken.RevisedLifetime = (uint)token.Lifetime;
            response.ServerNonce = token.ServerNonce;

            byte[] buffer = BinaryEncoder.EncodeMessage(response, Quotas.MessageContext);

            BufferCollection chunksToSend = WriteAsymmetricMessage(
                TcpMessageType.Open,
                requestId,
                ServerCertificate,
                ServerCertificateChain,
                ClientCertificate,
                new ArraySegment<byte>(buffer, 0, buffer.Length));

            // write the message to the server.
            try
            {
                BeginWriteMessage(chunksToSend, null);
                chunksToSend = null;
            }
            finally
            {
                chunksToSend?.Release(BufferManager, "SendOpenSecureChannelResponse");
            }
        }

        /// <summary>
        /// Processes an CloseSecureChannel request message.
        /// </summary>
        /// <exception cref="ServiceResultException"></exception>
        private bool ProcessCloseSecureChannelRequest(
            uint messageType,
            ArraySegment<byte> messageChunk)
        {
            // Communication is active on the channel
            UpdateLastActiveTime();

            // validate security on the message.
            ChannelToken token = null;
            uint requestId = 0;
            uint sequenceNumber = 0;

            ArraySegment<byte> messageBody;

            try
            {
                messageBody = ReadSymmetricMessage(
                    messageChunk,
                    true,
                    out token,
                    out requestId,
                    out sequenceNumber);

                // check for replay attacks.
                if (!VerifySequenceNumber(sequenceNumber, "ProcessCloseSecureChannelRequest"))
                {
                    throw new ServiceResultException(
                        StatusCodes.BadSequenceNumberInvalid,
                        "Could not verify security on CloseSecureChannel request.");
                }
            }
            catch (Exception e)
            {
                // report the audit event for close secure channel
                ReportAuditCloseSecureChannelEvent?.Invoke(this, e);

                throw ServiceResultException.Create(
                    StatusCodes.BadSecurityChecksFailed,
                    e,
                    "Could not verify security on CloseSecureChannel request.");
            }

            BufferCollection chunksToProcess = null;

            try
            {
                // check if it is necessary to wait for more chunks.
                if (!TcpMessageType.IsFinal(messageType))
                {
                    SaveIntermediateChunk(requestId, messageBody, true);
                    return false;
                }

                // get the chunks to process.
                chunksToProcess = GetSavedChunks(requestId, messageBody, true);

                if (BinaryDecoder.DecodeMessage(
                        new ArraySegmentStream(chunksToProcess),
                        typeof(CloseSecureChannelRequest),
                        Quotas.MessageContext)
                    is not CloseSecureChannelRequest request)
                {
                    throw ServiceResultException.Create(
                        StatusCodes.BadStructureMissing,
                        "Could not parse CloseSecureChannel request body.");
                }

                // send the response.
                // SendCloseSecureChannelResponse(requestId, token, request);

                // report the audit event for close secure channel
                ReportAuditCloseSecureChannelEvent?.Invoke(this, null);
            }
            catch (Exception e)
            {
                // report the audit event for close secure channel
                ReportAuditCloseSecureChannelEvent?.Invoke(this, e);

                m_logger.LogError(e, "Unexpected error processing CloseSecureChannel request.");
            }
            finally
            {
                chunksToProcess?.Release(BufferManager, "ProcessCloseSecureChannelRequest");

                m_logger.LogInformation(
                    "{Channel} ProcessCloseSecureChannelRequest success, ChannelId={ChannelId}, TokenId={TokenId}, Socket={SocketHandle:X8}",
                    ChannelName,
                    CurrentToken?.ChannelId,
                    CurrentToken?.TokenId,
                    Socket?.Handle);

                // close the channel.
                ChannelClosed();
            }

            // return false would double free the buffer
            return true;
        }

        /// <summary>
        /// Processes a request message.
        /// </summary>
        /// <exception cref="ServiceResultException"></exception>
        private bool ProcessRequestMessage(uint messageType, ArraySegment<byte> messageChunk)
        {
            // Communication is active on the channel
            UpdateLastActiveTime();

            // validate the channel state.
            if (State != TcpChannelState.Open)
            {
                ForceChannelFault(
                    StatusCodes.BadTcpMessageTypeInvalid,
                    "Client sent an unexpected request message.");
                return false;
            }

            ArraySegment<byte> messageBody;

            // validate security on the message.
            ChannelToken token;

            uint requestId;
            try
            {
                messageBody = ReadSymmetricMessage(
                    messageChunk,
                    true,
                    out token,
                    out requestId,
                    out uint sequenceNumber);

                // check for replay attacks.
                if (!VerifySequenceNumber(sequenceNumber, "ProcessRequestMessage"))
                {
                    throw new ServiceResultException(StatusCodes.BadSequenceNumberInvalid);
                }

                if (token == CurrentToken && PreviousToken != null && !PreviousToken.Expired)
                {
                    m_logger.LogInformation(
                        "ChannelId {Id}: Server Current Token #{CurrentToken}, Revoked Token #{PreviousToken}.",
                        PreviousToken.ChannelId,
                        CurrentToken.TokenId,
                        PreviousToken.TokenId);
                    PreviousToken.Lifetime = 0;
                }
            }
            catch (Exception e)
            {
                ForceChannelFault(
                    e,
                    StatusCodes.BadSecurityChecksFailed,
                    "Could not verify security on incoming request.");
                return false;
            }

            int countForDisconnect = 5;
            while (ChannelFull && countForDisconnect > 0)
            {
                m_logger.LogInformation("Channel {Id}: full -- delay processing.", Id);

                // delay reading from channel
                Thread.Sleep(1000);

                if (--countForDisconnect == 0 && ChannelFull)
                {
                    m_logger.LogWarning("Channel {Id}: break socket connection.", Id);
                    ChannelClosed();
                    return false;
                }
            }

            BufferCollection chunksToProcess = null;

            try
            {
                // check for an abort.
                if (TcpMessageType.IsAbort(messageType))
                {
                    m_logger.LogWarning(
                        Utils.TraceMasks.ServiceDetail,
                        "ChannelId {Id}: ProcessRequestMessage RequestId {RequestId} was aborted.",
                        ChannelId,
                        requestId);
                    chunksToProcess = GetSavedChunks(requestId, messageBody, true);
                    return true;
                }

                // check if it is necessary to wait for more chunks.
                if (!TcpMessageType.IsFinal(messageType))
                {
                    bool firstChunk = SaveIntermediateChunk(requestId, messageBody, true);

                    // validate the type is allowed with a discovery channel
                    if (DiscoveryOnly)
                    {
                        if (firstChunk)
                        {
                            if (!ValidateDiscoveryServiceCall(
                                token,
                                requestId,
                                messageBody,
                                out chunksToProcess))
                            {
                                ChannelClosed();
                            }
                        }
                        else if (GetSavedChunksTotalSize() > TcpMessageLimits
                            .DefaultDiscoveryMaxMessageSize)
                        {
                            chunksToProcess = GetSavedChunks(0, messageBody, true);
                            SendServiceFault(
                                token,
                                requestId,
                                ServiceResult.Create(
                                    StatusCodes.BadSecurityPolicyRejected,
                                    "Discovery Channel message size exceeded."));
                            ChannelClosed();
                        }
                    }

                    return true;
                }

                // m_logger.LogTrace(
                //      "ChannelId {Id}: ProcessRequestMessage RequestId {RequestId}",
                //      ChannelId,
                //      requestId);

                if (DiscoveryOnly &&
                    GetSavedChunksTotalSize() == 0 &&
                    !ValidateDiscoveryServiceCall(
                        token,
                        requestId,
                        messageBody,
                        out chunksToProcess))
                {
                    return true;
                }

                // get the chunks to process.
                chunksToProcess = GetSavedChunks(requestId, messageBody, true);

                // decode the request.
                if (BinaryDecoder.DecodeMessage(
                        new ArraySegmentStream(chunksToProcess),
                        null,
                        Quotas.MessageContext)
                    is not IServiceRequest request)
                {
                    SendServiceFault(
                        token,
                        requestId,
                        ServiceResult.Create(
                            StatusCodes.BadStructureMissing,
                            "Could not parse request body."));
                    return true;
                }

                // ensure that only discovery requests come through unsecured.
                if (DiscoveryOnly &&
                    request is not (GetEndpointsRequest or FindServersRequest or FindServersOnNetworkRequest))
                {
                    SendServiceFault(
                        token,
                        requestId,
                        ServiceResult.Create(
                            StatusCodes.BadSecurityPolicyRejected,
                            "Channel can only be used for discovery."));
                    return true;
                }

                // hand the request to the server.
                RequestReceived?.Invoke(this, requestId, request);

                return true;
            }
            catch (Exception e)
            {
                m_logger.LogError(e, "Unexpected error processing request.");
                SendServiceFault(
                    token,
                    requestId,
                    ServiceResult.Create(
                        e,
                        StatusCodes.BadTcpInternalError,
                        "Unexpected error processing request."));
                return true;
            }
            finally
            {
                chunksToProcess?.Release(BufferManager, "ProcessRequestMessage");
            }
        }

        /// <summary>
        /// Sends the response for the specified request.
        /// </summary>
        /// <exception cref="ArgumentNullException"><paramref name="response"/> is <c>null</c>.</exception>
        /// <exception cref="ServiceResultException"></exception>
        public void SendResponse(uint requestId, IServiceResponse response)
        {
            if (response == null)
            {
                throw new ArgumentNullException(nameof(response));
            }

            lock (DataLock)
            {
                // must queue the response if the channel is in the faulted state.
                if (State == TcpChannelState.Faulted)
                {
                    m_queuedResponses[requestId] = response;
                    return;
                }

                // if the channel is closed no response can be sent, throw exception to end processing in this specific channel.
                if (State == TcpChannelState.Closed)
                {
                    throw new ServiceResultException(
                        StatusCodes.BadSecureChannelClosed,
                        "Cannot send response over a closed channel.");
                }

                Utils.EventLog.SendResponse((int)ChannelId, (int)requestId);
                // m_logger.LogTrace(
                //     "ChannelId {ChannelId}: SendResponse {RequestId}",
                //     ChannelId,
                //     requestId);
                BufferCollection buffers = null;

                try
                {
                    // note that the server does nothing if the message limits are exceeded.

                    buffers = WriteSymmetricMessage(
                        TcpMessageType.Message,
                        requestId,
                        CurrentToken,
                        response,
                        false,
                        out bool limitsExceeded);
                }
                catch (Exception e)
                {
                    SendServiceFault(
                        CurrentToken,
                        requestId,
                        ServiceResult.Create(
                            e,
                            StatusCodes.BadEncodingError,
                            "Could not encode outgoing message."));

                    return;
                }

                try
                {
                    BeginWriteMessage(buffers, null);
                    buffers = null;
                }
                catch (Exception)
                {
                    buffers?.Release(BufferManager, "SendResponse");

                    m_queuedResponses[requestId] = response;
                    return;
                }

                if (response is ActivateSessionResponse activateSessionResponse)
                {
                    UsedBySession = StatusCode.IsGood(
                        activateSessionResponse.ResponseHeader.ServiceResult);
                }
            }
        }

        /// <summary>
        /// Reset the sorted dictionary of queued responses after reconnect.
        /// </summary>
        private void ResetQueuedResponses(Action<object> action)
        {
            Task.Factory.StartNew(
                action,
                m_queuedResponses,
                default,
                TaskCreationOptions.DenyChildAttach,
                TaskScheduler.Default);
            m_queuedResponses = [];
        }

        /// <summary>
        /// Closes the channel in case the message limits have been exceeded
        /// </summary>
        protected override void DoMessageLimitsExceeded()
        {
            base.DoMessageLimitsExceeded();
            ChannelClosed();
        }

        /// <summary>
        /// Validate the type of message before it is decoded.
        /// </summary>
        private bool ValidateDiscoveryServiceCall(
            ChannelToken token,
            uint requestId,
            ArraySegment<byte> messageBody,
            out BufferCollection chunksToProcess)
        {
            chunksToProcess = null;
            using var decoder = new BinaryDecoder(messageBody, Quotas.MessageContext);
            // read the type of the message before more chunks are processed.
            NodeId typeId = decoder.ReadNodeId(null);

            if (typeId != ObjectIds.GetEndpointsRequest_Encoding_DefaultBinary &&
                typeId != ObjectIds.FindServersRequest_Encoding_DefaultBinary &&
                typeId != ObjectIds.FindServersOnNetworkRequest_Encoding_DefaultBinary)
            {
                chunksToProcess = GetSavedChunks(0, messageBody, true);
                SendServiceFault(
                    token,
                    requestId,
                    ServiceResult.Create(
                        StatusCodes.BadSecurityPolicyRejected,
                        "Channel can only be used for discovery."));
                return false;
            }
            return true;
        }

        private readonly ILogger m_logger;
        private SortedDictionary<uint, IServiceResponse> m_queuedResponses;
        private ReverseConnectAsyncResult m_pendingReverseHello;

        private static readonly string s_implementationString =
            ".NET Standard ServerChannel UA-TCP " + Utils.GetAssemblyBuildNumber();
    }
}
